package close.mazegame.entity;

import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.JointDef.JointType;
import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef;

import close.mazegame.art.tanks.TankTexture;
import close.mazegame.collision.Collidable;
import close.mazegame.entity.effect.Explosion;
import close.mazegame.entity.effect.SmokePuff;
import close.mazegame.entity.projectile.TestProjectile;
import close.mazegame.level.Level;
import close.mazegame.screens.Screen;
import close.util.AngleMath;

public abstract class Tank extends PhysicsEntity implements Collidable {
	
	public static final float BASE_FIRE_INTERVAL = 1.0f;
	public static final float BASE_SPEED = 3.0f;
	
	public static final float LINEAR_GROUND_FRICTION = 4.0f;
	public static final float ANGULAR_GROUND_FRICTION = 20.0f;
	
	protected Turret turret;
	protected boolean hasTurret;
	
	protected float fireIntervalMod = 1.0f;
	protected float lastFire = 0.0f;
	protected float speed;
	
	protected class Turret extends PhysicsEntity {
		
		public static final float TURRET_OFFSET = 0.03f;
		public static final float ANGULAR_TURRET_FRICTION = 40.0f;
		
		protected Tank tank; //The tank that this turret is attached to
		protected boolean fixed;

		public Turret(Tank tank, TextureRegion texture) {
			super(tank.getLevel());
			this.tank = tank;
			this.mainTexture = texture;
			this.fixed = false;
			
			Vector2 pos = tank.getPosition();
			
			BodyDef bodyDef = new BodyDef();
			bodyDef.type = BodyType.DynamicBody;
			bodyDef.position.set(pos.x - getXSize() / 2.0f + TURRET_OFFSET, pos.y);
			body = level.getWorld().createBody(bodyDef);

			FixtureDef fixtureDef = new FixtureDef();
			PolygonShape poly = new PolygonShape();
			fixtureDef.filter.categoryBits = getCategoryBits();
			poly.setAsBox(getXSize() / 2, getYSize() / 2);
			fixtureDef.shape = poly;
			fixtureDef.density = 1.0f;
			body.createFixture(fixtureDef);
			poly.dispose();

			body.setAngularDamping(ANGULAR_TURRET_FRICTION);

			RevoluteJointDef jDef = new RevoluteJointDef();
			jDef.type = JointType.RevoluteJoint;
			jDef.bodyA = tank.body;
			jDef.bodyB = body;
			jDef.localAnchorB.set(-getXSize() / 2.0f + TURRET_OFFSET, 0.0f);
			
			level.getWorld().createJoint(jDef);

			//Allow the tank object to handle collisions with the turret in the same way as the main body
			body.setUserData(tank);
		}

		@Override
		public float getXSize() {
			return (tank.getXSize() / 2.0f) + TURRET_OFFSET * 2.0f;
		}

		@Override
		public float getYSize() {
			return this.getXSize() * (24f / 58f);
		}
		
		public void move(float delta, float targetTurretAngle) {
			if (fixed) {
				targetTurretAngle = (float) Math.toDegrees(tank.body.getAngle()) % 360;
			}
			float turretAngle = (float) Math.toDegrees(this.body.getAngle()) % 360;
			float dAng2 = AngleMath.angleDifference(targetTurretAngle, turretAngle);
			this.body.applyTorque(dAng2 / 360, true);
		}
		
		public boolean isFixed(){
			return fixed;
		}
		
		public void setFixed(boolean fixed) {
			this.fixed = fixed;
		}
		
		@Override
		public void update(float delta) {
		}
		
		public Vector2 getEnd(float safety) {
			Vector2 end = new Vector2(this.getPosition());
			float dist = (getXSize() + safety)/2.0f; //Distance from centre of turret
			end.add((float)Math.cos(getAngle())*dist, (float)Math.sin(getAngle())*dist);
			return end;
		}
		
		public Vector2 getEnd() {
			return getEnd(0.0f);
		}
		
	}

	public Tank(Vector2 pos, Level level, TankTexture texture, boolean hasTurret) {
		super(level);

		speed = BASE_SPEED;

		mainTexture = texture.body;

		createMainBody(pos);
		
		this.hasTurret = hasTurret;
		if (hasTurret) {
			createTurret(texture.barrel);
		}
	}

	public void move(float delta, Vector2 dir, float targetTurretAngle) {
		lastFire += delta;

		float ang = (float) Math.toDegrees(body.getAngle()) % 360;
		if (dir.len2() != 0) {
			
			
			if (dir.len2() > 1.0f) {
				dir.nor();
			}
			
			dir.scl(speed);
			body.applyForceToCenter(dir, true);
			float dAng = AngleMath.angleDifference(dir.angle(), ang);
			body.applyTorque(dAng / 90, true);
		}
		
		if (hasTurret) {
			turret.move(delta, targetTurretAngle);
		}
	}

	public void render(Screen screen) {
		super.render(screen);
		if (hasTurret) {
			turret.render(screen);
		}
	}

	private void createMainBody(Vector2 pos) {
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DynamicBody;
		bodyDef.position.set(pos);
		body = level.getWorld().createBody(bodyDef);

		PolygonShape poly = new PolygonShape();
		poly.setAsBox(getXSize() / 2.0f, getYSize() / 2.0f);
		FixtureDef fixtureDef = new FixtureDef();
		fixtureDef.filter.categoryBits = getCategoryBits();
		fixtureDef.shape = poly;
		fixtureDef.density = 4.0f;
		fixtureDef.friction = 0.0f;
		fixtureDef.restitution = 0.1f;
		body.createFixture(fixtureDef);
		poly.dispose();

		body.setAngularDamping(ANGULAR_GROUND_FRICTION);
		body.setLinearDamping(LINEAR_GROUND_FRICTION);
		
		body.setUserData(this);
	}
	
	private void createTurret(TextureRegion texture){
		this.turret = new Turret(this, texture);
	}

	protected abstract short getCategoryBits();

	private boolean canFire() {
		if (lastFire > BASE_FIRE_INTERVAL * fireIntervalMod)
			return true;
		return false;
	}

	public void fireProjectile() {
		float angle = (float) Math.toDegrees(turret.getAngle()) % 360;
		if (!canFire()) {
			return;
		}
		lastFire = 0;
		
		Vector2 projectilePos = turret.getEnd(0.2f);
		level.add(new TestProjectile(projectilePos, level, angle));
		
		Vector2 smokePos = turret.getEnd();
		level.add(new SmokePuff(smokePos, this.getLevel()));

		
	}
	
	public void explode() {
		this.remove();
		level.add(new Explosion(this.getPosition(), this.getLevel()));
	}

	public void dispose() {
		super.dispose();
		if (hasTurret) {
			turret.dispose();
		}
	}

	@Override
	public float getXSize() {
		return 0.25f * (41f / 39f);
	}
	
	@Override
	public float getYSize() {
		return 0.25f;
	}

	public Vector2 getVelocity() {
		return body.getLinearVelocity();
	}

	@Override
	public float getMass() {
		return (body.getMass() + (hasTurret ? turret.getMass() : 0));
	}
	
	public Vector2 getPositionEngine() {
		Vector2 vec = new Vector2(this.getPosition());
		float dist = getXSize()*0.4f; //Distance from centre of tank
		vec.sub((float)Math.cos(getAngle())*dist, (float)Math.sin(getAngle())*dist);
		return vec;
	}

}
